﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xrm.Sdk.Workflow;
using System.Activities;
using Microsoft.Xrm.Sdk;
using Microsoft.Crm.Sdk.Messages;
using Microsoft.Crm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using System.Xml.Serialization;
using System.IO;
using System.Runtime.Serialization;
using System.Xml;

/**
 * Copyright 2011 by Alberto Gemin
 * agemin@hotmail.com
 * Version 1.7.0.1
 * 13 Dec 2011
 **/
namespace Gemina.CRM2011.WF.LdgManager
{
    [AgCodeActivity(ActivityNameDeployUserView, AgViewsUtils.GroupName)]
    public class AgDeployUserView : AgDeployView
    {

        #region Constants

        private const string ActivityNameDeployUserView = "Deploy User View";
        private const string Param1InputViewId = "View Id";
        private const string Param2InputWhenExistingAction = "When Existing";
        private const string Param3InputUserQueryOwner = "Personal View Owner";
        private const string Param4InputUserQueryName = "Personal View Name";
        private const string Param5InputUserViewReturnedType = "Personal View Returned Entity";
        private const string Param6InputUserQueryType = "Personal View Query Type";
        private const string Param7InputFetchXml = "Fetch XML";
        private const string Param8InputLayoutXml = "Layout XML";
        private const string Param9InputViewDefinition = "View Definition";
        private const string MsgAction = "{0} is not a valid value for {1}. Valid values are \"{2}\", \"{3}\", \"{4}\" and \"{5}\".";
        private const string MsgOperationError = "View {0} already exists and \"{1}\" action was specified, or multiple views were found.";
        private const string MsgActionNotAllowed = "Action {0} is not allowed when {1} is populated. Valid values are \"{2}\" and \"{3}\".";
        private const string MsgActionNotAllowed2 = "Action {0} is not allowed when {1} is null.";
        private const string MsgBothNull = "{0} and {1} cannot be both null.";
        private const string MsgNull = "{0} cannot be both null.";

        #endregion

        #region Properties

        public EntityReference ValidatedUserQueryOwner { get; set; }

        #endregion

        #region Workflow Input Parameters

        /// <summary>
        /// Input parameter
        /// </summary>
        [Input(Param1InputViewId)]
        public InArgument<String> InputViewId { get; set; }

        /// <summary>
        /// Input parameter
        /// </summary>
        [Input(Param2InputWhenExistingAction)]
        [RequiredArgument]
        public InArgument<String> InputWhenExistingAction { get; set; }

        /// <summary>
        /// Input parameter
        /// </summary>
        [Input(Param3InputUserQueryOwner)]
        [ReferenceTarget(AgViewsUtils.EntityNameSystemUser)]
        [RequiredArgument]
        public InArgument<EntityReference> InputUserQueryOwner { get; set; }

        /// <summary>
        /// Input parameter
        /// </summary>
        [Input(Param4InputUserQueryName)]
        public InArgument<String> InputUserQueryName { get; set; }

        /// <summary>
        /// Input parameter
        /// </summary>
        [Input(Param5InputUserViewReturnedType)]
        public InArgument<String> InputReturnedType { get; set; }

        /// <summary>
        /// Input parameter
        /// </summary>
        [Input(Param6InputUserQueryType)]
        public InArgument<String> InputQueryType { get; set; }

        /// <summary>
        /// Input parameter
        /// </summary>
        [Input(Param7InputFetchXml)]
        public InArgument<String> InputFetchXml { get; set; }

        /// <summary>
        /// Input parameter
        /// </summary>
        [Input(Param8InputLayoutXml)]
        public InArgument<String> InputLayoutXml { get; set; }

        /// <summary>
        /// Input parameter
        /// </summary>
        [Input(Param9InputViewDefinition)]
        public InArgument<String> InputViewDefinition { get; set; }

        #endregion

        #region CodeActivity

        override protected bool ExecuteBody(CodeActivityContext executionContext)
        {
            bool byId = this.CheckArguments(executionContext);

            Entity newEntity = this.CreateEntity();

            IOrganizationService svc;
            if (this.ValidatedUserQueryOwner != null)
            {
                svc = this.GetService(executionContext, this.ValidatedUserQueryOwner.Id);
            }
            else
            {
                svc = this.GetService(executionContext);
            }

            Guid viewId = Guid.Empty;
            ActionEnum action;
            if (!byId)
            {
                action = this.CheckExistence(executionContext, AgViewsUtils.EntityNameUserQuery, out viewId);
            }
            else
            {
                action = this.ValidatedAction;
            }

            return this.PerformAction(executionContext, action, newEntity, viewId, byId, this.ValidatedQueryName);
        }

        #endregion

        #region Local Methods

        private Entity CreateEntity()
        {
            Entity deserializedView = null;
            if (!string.IsNullOrEmpty(this.ValidatedViewDefinition))
            {
                deserializedView = this.DeserializeEntity(this.ValidatedViewDefinition);
            }

            Entity newView = new Entity(AgViewsUtils.EntityNameUserQuery);

            // Derive QueryType if possible
            if (this.ValidatedQueryType == null && deserializedView != null && deserializedView.Attributes.Contains(AgViewsUtils.FieldQueryType))
            {
                this.ValidatedQueryType = (int)deserializedView.Attributes[AgViewsUtils.FieldQueryType];
                AgViewsUtils.GetQueryType(this.ValidatedQueryType.ToString(), false, Param6InputUserQueryType, Param4InputUserQueryName);
            }
            if (this.ValidatedQueryType != null)
            {
                newView.Attributes.Add(AgViewsUtils.FieldQueryType, this.ValidatedQueryType);
            }

            // Derive FetchXml if possible
            if (this.ValidatedFetchXml == null && deserializedView != null && deserializedView.Attributes.Contains(AgViewsUtils.FieldFetchXml))
            {
                this.ValidatedFetchXml = (string)deserializedView.Attributes[AgViewsUtils.FieldFetchXml];
            }
            if (this.ValidatedFetchXml != null)
            {
                this.ValidatedFetchXml = CleanupFetchXml(this.ValidatedQueryType, this.ValidatedFetchXml);
                newView.Attributes.Add(AgViewsUtils.FieldFetchXml, this.ValidatedFetchXml);
            }

            // Derive LayoutXml if possible
            if (this.ValidatedLayoutXml == null && deserializedView != null && deserializedView.Attributes.Contains(AgViewsUtils.FieldLayoutXml))
            {
                this.ValidatedLayoutXml = (string)deserializedView.Attributes[AgViewsUtils.FieldLayoutXml];
            }
            if (this.ValidatedLayoutXml != null)
            {
                newView.Attributes.Add(AgViewsUtils.FieldLayoutXml, this.ValidatedLayoutXml);
            }

            // Derive Name if possible
            if (this.ValidatedQueryName == null && deserializedView != null && deserializedView.Attributes.Contains(AgViewsUtils.FieldName))
            {
                this.ValidatedQueryName = (string)deserializedView.Attributes[AgViewsUtils.FieldName];
            }
            if (this.ValidatedQueryName != null)
            {
                newView.Attributes.Add(AgViewsUtils.FieldName, this.ValidatedQueryName);
            }

            // Derive ReturnedType if possible
            if (this.ValidatedReturnedType == null && deserializedView != null && deserializedView.Attributes.Contains(AgViewsUtils.FieldReturnedTypeCode))
            {
                this.ValidatedReturnedType = (string)deserializedView.Attributes[AgViewsUtils.FieldReturnedTypeCode];
            }
            if (this.ValidatedReturnedType != null)
            {
                newView.Attributes.Add(AgViewsUtils.FieldReturnedTypeCode, this.ValidatedReturnedType);
            }

            // Derive Description if possible
            if (deserializedView != null && deserializedView.Attributes.Contains(AgViewsUtils.FieldDescription))
            {
                this.ValidatedDescription = (string)deserializedView.Attributes[AgViewsUtils.FieldDescription];
            }
            if (this.ValidatedDescription != null)
            {
                newView.Attributes.Add(AgViewsUtils.FieldDescription, this.ValidatedDescription);
            }

            // Derive Id if provided
            if (this.ValidatedViewId != null)
            {
                newView.Id = new Guid(this.ValidatedViewId);
            }

            return newView;
        }

        private bool PerformAction(CodeActivityContext executionContext, ActionEnum action, Entity newEntity, Guid viewId, bool byId, string queryName)
        {
            IOrganizationService svc;
            if (this.ValidatedUserQueryOwner != null)
            {
                svc = this.GetService(executionContext, this.ValidatedUserQueryOwner.Id);
            }
            else
            {
                svc = this.GetService(executionContext);
            }

            switch (action)
            {
                case ActionEnum.Create:
                    viewId = svc.Create(newEntity);
                    this.OutputViewId.Set(executionContext, viewId.ToString());
                    this.OutputResultAction.Set(executionContext, ActionEnum.Create.ToString());
                    break;

                case ActionEnum.Update:
                    if (!byId)
                    {
                        newEntity.Id = viewId;
                    }
                    svc.Update(newEntity);
                    this.OutputViewId.Set(executionContext, newEntity.Id.ToString());
                    this.OutputResultAction.Set(executionContext, ActionEnum.Update.ToString());
                    break;

                case ActionEnum.Delete:
                    svc.Delete(newEntity.LogicalName, newEntity.Id);
                    this.OutputResultAction.Set(executionContext, ActionEnum.Delete.ToString());
                    break;

                case ActionEnum.Ignore:
                    this.OutputResultAction.Set(executionContext, ActionEnum.Ignore.ToString());
                    break;

                default:
                    throw new AgErrorMessage(string.Format(MsgOperationError, queryName, ActionEnum.Error.ToString()));
            }

            return true;
        }

        private ActionEnum CheckExistence(CodeActivityContext executionContext, string entityNameQuery, out Guid viewId)
        {
            QueryExpression query = new QueryExpression
            {
                EntityName = entityNameQuery
            };

            // Add Name condition if present
            if (this.ValidatedQueryName != null)
            {
                query.Criteria.AddCondition(new ConditionExpression
                {
                    AttributeName = AgViewsUtils.FieldName,
                    Operator = ConditionOperator.Equal,
                    Values = { this.ValidatedQueryName }
                });
            }

            // Add ReturnedType condition if present
            if (this.ValidatedReturnedType != null)
            {
                query.Criteria.AddCondition(new ConditionExpression
                {
                    AttributeName = AgViewsUtils.FieldReturnedTypeCode,
                    Operator = ConditionOperator.Equal,
                    Values = { this.ValidatedReturnedType }
                });
            }

            // Add QueryType condition if present
            if (this.ValidatedQueryType != null)
            {
                query.Criteria.AddCondition(new ConditionExpression
                {
                    AttributeName = AgViewsUtils.FieldQueryType,
                    Operator = ConditionOperator.Equal,
                    Values = { this.ValidatedQueryType }
                });
            }

            IOrganizationService svc;
            if (this.ValidatedUserQueryOwner != null)
            {
                svc = this.GetService(executionContext, this.ValidatedUserQueryOwner.Id);
            }
            else
            {
                svc = this.GetService(executionContext);
            }

            // check whether the view exists based on the conditions above
            DataCollection<Entity> responses = svc.RetrieveMultiple(query).Entities;

            viewId = Guid.Empty;
            if (responses.Count == 0)
            {
                return ActionEnum.Create;
            }
            else if (responses.Count == 1)
            {
                viewId = responses[0].Id;
                return this.ValidatedAction; // ignore, update or error
            }
            else
            {
                return ActionEnum.Error;
            }
        }

        private bool CheckArguments(CodeActivityContext executionContext)
        {
            string queryType = this.InputQueryType.Get(executionContext);

            this.ValidatedUserQueryOwner = this.InputUserQueryOwner.Get(executionContext);
            this.ValidatedQueryName = this.InputUserQueryName.Get(executionContext);
            this.ValidatedReturnedType = this.InputReturnedType.Get(executionContext);
            this.ValidatedFetchXml = this.InputFetchXml.Get(executionContext);
            this.ValidatedLayoutXml = this.InputLayoutXml.Get(executionContext);
            this.ValidatedViewDefinition = this.InputViewDefinition.Get(executionContext);
            this.ValidatedViewId = this.InputViewId.Get(executionContext);

            if (this.ValidatedUserQueryOwner == null)
            {
                throw new AgErrorMessage(String.Format(MsgNull, Param3InputUserQueryOwner));
            }

            string action = this.InputWhenExistingAction.Get(executionContext);
            if (ActionEnum.Update.ToString().Equals(action, StringComparison.CurrentCultureIgnoreCase))
            {
                this.ValidatedAction = ActionEnum.Update;
            }
            else if (ActionEnum.Ignore.ToString().Equals(action, StringComparison.CurrentCultureIgnoreCase))
            {
                this.ValidatedAction = ActionEnum.Ignore;
            }
            else if (ActionEnum.Delete.ToString().Equals(action, StringComparison.CurrentCultureIgnoreCase))
            {
                this.ValidatedAction = ActionEnum.Delete;
            }
            else if (ActionEnum.Error.ToString().Equals(action, StringComparison.CurrentCultureIgnoreCase) || string.IsNullOrEmpty(action))
            {
                this.ValidatedAction = ActionEnum.Error;
            }
            else
            {
                throw new AgErrorMessage(string.Format(MsgAction, action, Param2InputWhenExistingAction,
                   ActionEnum.Error.ToString(), ActionEnum.Ignore.ToString(), ActionEnum.Delete.ToString(), ActionEnum.Update.ToString()));
            }

            this.ValidatedQueryType = AgViewsUtils.GetQueryType(queryType, false, Param6InputUserQueryType, Param4InputUserQueryName);

            if (!string.IsNullOrEmpty(this.ValidatedViewId))
            {
                this.CheckArgumentsForId();
                return true;
            }
            else
            {
                this.CheckArgumentsForNoId(Param5InputUserViewReturnedType, Param4InputUserQueryName, Param6InputUserQueryType);
                return false;
            }
        }

        private void CheckArgumentsForId()
        {
            if (this.ValidatedAction == ActionEnum.Error || this.ValidatedAction == ActionEnum.Ignore)
            {
                throw new AgErrorMessage(string.Format(MsgActionNotAllowed, this.ValidatedAction, Param1InputViewId, ActionEnum.Delete.ToString(), ActionEnum.Update.ToString()));
            }
        }

        private void CheckArgumentsForNoId(string paramInputReturnedType, string paramInputQueryName, string paramInputQueryType)
        {
            if (this.ValidatedAction == ActionEnum.Delete)
            {
                throw new AgErrorMessage(string.Format(MsgActionNotAllowed2, this.ValidatedAction, Param1InputViewId));
            }
            if (string.IsNullOrEmpty(this.ValidatedFetchXml) && string.IsNullOrEmpty(ValidatedViewDefinition))
            {
                throw new AgErrorMessage(string.Format(MsgBothNull, Param7InputFetchXml, Param9InputViewDefinition));
            }
            if (string.IsNullOrEmpty(this.ValidatedReturnedType) && string.IsNullOrEmpty(ValidatedViewDefinition))
            {
                throw new AgErrorMessage(string.Format(MsgBothNull, paramInputReturnedType, Param9InputViewDefinition));
            }
            if (string.IsNullOrEmpty(this.ValidatedQueryName) && string.IsNullOrEmpty(ValidatedViewDefinition))
            {
                throw new AgErrorMessage(string.Format(MsgBothNull, paramInputQueryName, Param9InputViewDefinition));
            }
            if (this.ValidatedQueryType == null && string.IsNullOrEmpty(ValidatedViewDefinition))
            {
                throw new AgErrorMessage(string.Format(MsgBothNull, paramInputQueryType, Param9InputViewDefinition));
            }
        }

        #endregion

    }
}
